///<reference path="../../typings/index.d.ts" />
import WechatCore from './core'
import * as EventEmitter from 'events'
import _debug from 'debug';
const debug = _debug('wechat')
const _ = require('lodash');
import {
    getCONF,
    isStandardBrowserEnv,
    assert
} from './util'

import {
    Contact,
    Contact_Data,
    getDisplayName
} from './interface/contact'
import {
    Message,
    Message_Data
} from './interface/message'


if (!isStandardBrowserEnv) {
    process.on('uncaughtException', (err: any) => {
        console.log('uncaughtException', err)
    })
}
export interface EventCallHandle {
    uuid(uuid: string): void;
    user_avatar(dataUrl: string): void;
    login(): void;
    logout(): void;
    contacts_updated(contacts: Contact[]): void;
    message(msg: Message): void;
    error(err: any): void;
}

export default class Wechat extends WechatCore {
    static STATE = getCONF().STATE;
    private eventEmitter: EventEmitter = null;

    public _emit(eventname: string, param?: any) {
        if (this._eventCallHandle && this._eventCallHandle[eventname.replace(/\-/g, "_")]) {
            try {
                this._eventCallHandle[eventname.replace(/\-/g, "_")](param);
                return;
            } catch (e) {
                this.emit(eventname, param);
                return;
            }
        }
        this.emit(eventname, param);


    }
    public emit: any;
    public on: any;
    public state: string = this.CONF.STATE.init;
    public contacts: any = {};// 所有联系人
    private lastSyncTime: number = 0;
    private syncPollingId: number = 0;
    private syncErrorCount: number = 0;
    private checkPollingId: any = 0;
    private retryPollingId: any = 0;
    private _eventCallHandle: EventCallHandle = null;
    constructor(data?: any, eventCallHandle?: EventCallHandle) {
        super(data);
        this.eventEmitter = new EventEmitter();
        this.emit = this.eventEmitter.emit;
        this.on = this.eventEmitter.on;
        if (eventCallHandle) {
            this._eventCallHandle = eventCallHandle;
        }
    }




    public get friendList(): { username: string, nickname: string, py: string, avatar: string }[] {
        let members = []
        for (let key in this.contacts) {
            let member = this.contacts[key]
            members.push({
                username: member['UserName'],
                nickname: getDisplayName(member),
                py: member['RemarkPYQuanPin'] ? member['RemarkPYQuanPin'] : member['PYQuanPin'],
                avatar: member.AvatarUrl
            })
        }
        return members
    }

    public async sendMsg(msg: any, toUserName: string) {
        if (typeof msg !== 'object') {
            return await this.sendText(msg, toUserName);
        } else if (msg.emoticonMd5) {
            return await this.sendEmoticon(msg.emoticonMd5, toUserName);
        } else {
            let res = await this.uploadMedia(msg.file, msg.filename, toUserName);
            switch (res.ext) {
                case 'bmp':
                case 'jpeg':
                case 'jpg':
                case 'png':
                    return await this.sendPic(res.mediaId, toUserName)
                case 'gif':
                    return await this.sendEmoticon(res.mediaId, toUserName)
                case 'mp4':
                    return await this.sendVideo(res.mediaId, toUserName)
                default:
                    return await this.sendDoc(res.mediaId, res.name, res.size, res.ext, toUserName)
            }
        }
    }

    public async syncPolling(id = ++this.syncPollingId) {
        if (this.state !== this.CONF.STATE.login || this.syncPollingId !== id) {
            return
        }
        this.syncCheck().then(selector => {
            debug('Sync Check Selector: ', selector)
            if (+selector !== this.CONF.SYNCCHECK_SELECTOR_NORMAL) {
                return this.sync().then(data => {
                    this.syncErrorCount = 0
                    this.handleSync(data)
                })
            }
        }).then(() => {
            this.lastSyncTime = Date.now()
            this.syncPolling(id)
        }).catch(err => {
            if (this.state !== this.CONF.STATE.login) {
                return
            }
            debug(err)
            this._emit('error', err)
            if (++this.syncErrorCount > 2) {
                let err = new Error(`连续${this.syncErrorCount}次同步失败，5s后尝试重启`)
                debug(err)
                this._emit('error', err)
                clearTimeout(this.retryPollingId)
                setTimeout(() => this.restart(), 5 * 1000)
            } else {
                clearTimeout(this.retryPollingId)
                this.retryPollingId = setTimeout(() => this.syncPolling(id), 2000 * this.syncErrorCount)
            }
        })
    }


    private async _getContact(Seq: number = 0): Promise<Contact_Data[]> {
        let contacts: Contact_Data[] = [];
        // return this.getContact(Seq)
        //     .then(res => {
        //         contacts = res.MemberList || []
        //         if (res.Seq) {
        //             return this._getContact(res.Seq)
        //                 .then(_contacts => contacts = contacts.concat(_contacts || []))
        //         }
        //     })
        //     .then(() => {
        //         if (Seq == 0) {
        //             let emptyGroup =
        //                 contacts.filter(contact => contact.UserName.startsWith('@@') && contact.MemberCount == 0)
        //             if (emptyGroup.length != 0) {
        //                 return this.batchGetContact(emptyGroup)
        //                     .then(_contacts => contacts = contacts.concat(_contacts || []))
        //             }
        //         } else {
        //             return contacts
        //         }
        //     })
        //     .catch(err => {
        //         this.emit('error', err)
        //         return contacts
        //     })
        try {
            let res = await this.getContact(Seq);
            contacts = res.MemberList || [];
            if (res.Seq) {
                let _contacts = await this._getContact(res.Seq);
                contacts = contacts.concat(_contacts || []);
            }
            if (Seq == 0) {
                let emptyGroup = contacts.filter(contact => contact.UserName.startsWith('@@') && contact.MemberCount == 0);
                if (emptyGroup.length != 0) {
                    let _contacts = await this.batchGetContact(emptyGroup);
                    contacts = contacts.concat(_contacts || []);
                }
            }
            return contacts;

        } catch (err) {
            this._emit('error', err);
            return contacts;
        }
    }

    private async _init() {
        return this.init()
            .then(data => {
                // this.getContact() 这个接口返回通讯录中的联系人（包括已保存的群聊）
                // 临时的群聊会话在初始化的接口中可以获取，因此这里也需要更新一遍 contacts
                // 否则后面可能会拿不到某个临时群聊的信息
                this.updateContacts(data.ContactList)

                this.notifyMobile()
                    .catch(err => this._emit('error', err))
                this._getContact()
                    .then(contacts => {
                        debug('getContact count: ', contacts.length)
                        this.updateContacts(contacts)
                    })
                this.state = this.CONF.STATE.login
                this.lastSyncTime = Date.now()
                this.syncPolling()
                this.checkPolling()
                this._emit('login')
            })
    }

    private async _checkLogin(): Promise<any> {

        //   try {
        // assert.equal(this.state, this.CONF.STATE.uuid, "");
        let res = await this.checkLogin();
        if (res.code === 201 && res.userAvatar) {
            this._emit('user-avatar', res.userAvatar);
        }
        if (res.code !== 200) {
            debug('checkLogin: ', res.code)
            return this._checkLogin();
            // 
        } else {
            return res;
        }
        // } catch (err) {
        //     this.emit("error", err);
        // }
    }
    private async _login() {


        let uuid = await this.getUUID();
        debug('getUUID: ', uuid)
        this.state = this.CONF.STATE.uuid;
        this._emit('uuid', uuid);

        let res = await this._checkLogin();

        debug('checkLogin: ', res.redirect_uri)
        return await this.login();
    }


    public async start() {
        debug('启动中...')
        try {
            await this._login();
            this._init();
        } catch (err) {
            debug(err)
            this._emit('error', err);
            // await this.stop();
            this.stop();
        }
    }


    public async restart() {
        debug('重启中...')
        return this._init()
            .catch(err => {
                if (err.response) {
                    throw err
                } else {
                    let err = new Error('重启时网络错误，60s后进行最后一次重启')
                    debug(err)
                    this._emit('error', err)
                    return new Promise(resolve => {
                        setTimeout(resolve, 60 * 1000)
                    }).then(() => this.init())
                        .then(data => {
                            this.updateContacts(data.ContactList)
                        })
                }
            }).catch(err => {
                debug(err)
                this._emit('error', err)
                this.stop()
            })
    }
    public release() {
        debug('释放中...')
        clearTimeout(this.retryPollingId);
        clearTimeout(this.checkPollingId);
        this.state = this.CONF.STATE.logout;
    }
    public async stop() {
        debug('登出中...')
        clearTimeout(this.retryPollingId);
        clearTimeout(this.checkPollingId);
        await this.logout();
        this.state = this.CONF.STATE.logout
        this._emit('logout');
    }


    public async checkPolling() {
        if (this.state !== this.CONF.STATE.login) {
            return;
        }
        let interval = Date.now() - this.lastSyncTime;
        if (interval > 1 * 60 * 1000) {
            let err = new Error(`状态同步超过${interval / 1000}s未响应，5s后尝试重启`);
            debug(err)
            this._emit('error', err);
            clearTimeout(this.checkPollingId);
            //setTimeout(async () => await this.restart(), 5 * 1000);
            setTimeout(() => { this.restart() }, 5 * 1000);
        } else {
            // try {
            //     await this.notifyMobile();
            // } catch (err) {
            //     this._emit('error', err);
            // }
            // try {
            //     await this.sendMsg(this._getPollingMessage, this._getPollingTarget;
            // } catch (err) {
            //     this._emit('error', err);
            // }
            debug('心跳')
            this.notifyMobile()
                .catch(err => {
                    debug(err)
                    this._emit('error', err)
                })

            this.sendMsg(this._getPollingMessage, this._getPollingTarget)
                .catch(err => {
                    debug(err)
                    this._emit('error', err)
                })
            clearTimeout(this.checkPollingId);
            //this.checkPollingId = setTimeout(async () => await this.checkPolling(), this._getPollingInterval);
            this.checkPollingId = setTimeout(() => { this.checkPolling() }, this._getPollingInterval);
        }
    }

    public async handleSync(data?: any) {
        if (!data) {
            //  await this.restart();
            this.restart();
            return;
        }

        if (data.AddMsgCount) {
            debug('syncPolling messages count: ', data.AddMsgCount)
            this.handleMsg(data.AddMsgList);
            //await this.handleMsg(data.AddMsgList);
        }
        if (data.ModContactCount) {
            debug('syncPolling ModContactList count: ', data.ModContactCount)
            console.log("ModContactList")
            this.updateContacts(data.ModContactList);

        }
    }

    public async handleMsg(data: any[]) {
        for (let i = 0; i < data.length; i++) {
            let msg = data[i];
            try {
                if (!this.contacts[msg.FromUserName] || (msg.FromUserName.startsWith('@@') && this.contacts[msg.FromUserName].MemberCount == 0)) {
                    console.log(msg.FromUserName)
                    try {
                        let contacts = await this.batchGetContact([{
                            UserName: msg.FromUserName
                        }]);
                        this.updateContacts(contacts);

                    } catch (err) {
                        debug(err)
                        this._emit('error', err)
                    }

                }

                msg = new Message(msg, this);
                this._emit('message', msg);

                if (msg.MsgType === this.CONF.MSGTYPE_STATUSNOTIFY) {
                    let userList: any[] = msg.StatusNotifyUserName.split(',').filter((UserName: string) => !this.contacts[UserName]).map((UserName: string) => { return { UserName: UserName } });
                    try {
                        // await Promise.all(_.chunk(userList, 50).map(async (list: any) => {
                        //     let res = await this.batchGetContact(list);
                        //     this.updateContacts(res);
                        // }));
                        for (let i = 0; i < userList.length; i += 50) {
                            let list = userList.slice(i, i + 50)
                            let res = await this.batchGetContact(list);
                            debug('batchGetContact data length: ', res.length)
                            this.updateContacts(res)
                        }
                        // await Promise.all(_.chunk(userList, 50).map(list => {
                        //     return this.batchGetContact(list).then(res => {
                        //         this.updateContacts(res)
                        //     })
                        // }));

                    } catch (err) {
                        debug(err)
                        this._emit('error', err)
                    }
                }

                if (msg.ToUserName === 'filehelper' && msg.Content === '退出小助理' || /^(.\udf1a\u0020\ud83c.){3}$/.test(msg.Content)) {
                    //await this.stop();
                    this.stop();
                }

            } catch (err) {
                this._emit('error', err)
                debug(err)
            }

        }
    }

    public updateContacts(contacts: Contact_Data[]) {
        if (!contacts || contacts.length == 0) {
            return
        }
        contacts.forEach((contact: any) => {
            if (this.contacts[contact.UserName]) {
                let oldContact = this.contacts[contact.UserName]
                // 清除无效的字段
                for (let i in contact) {
                    contact[i] || delete contact[i]
                }
                Object.assign(oldContact, contact)
                //this.Contact.extend(oldContact)
                this.contacts[contact.UserName] = new Contact(oldContact, this)
            } else {
                this.contacts[contact.UserName] = new Contact(contact, this)//this.Contact.extend(contact)
            }
        })
        console.log(Object.keys(this.contacts).length);
        this._emit('contacts-updated', contacts);
    }

    private get _getPollingMessage(): string { // Default polling message
        return '小助理运行中:' + new Date().toLocaleString()
        //return '心跳：' + new Date().toLocaleString()
    }

    private get _getPollingInterval(): number { // Default polling interval
        return 5 * 60 * 1000
    }

    private get _getPollingTarget(): string { // Default polling target user
        return 'filehelper'
    }
}